<template>
  <view class="content">
    <view>
      <button @click="scan" class="scan-btn">刷新</button>
      <button @click="send" class="scan-btn" v-if="connectId && connected">发送数据</button>
    </view>

    <view style="color:green;padding: 22px 8px;font-size: 22px;">接收到数据：{{ received }}</view>

    <view class="dev-list">
      <h4>设备列表（{{ devices.length }}台）：</h4>
      <div v-for="item in devices" class="deviceItem" :key="item.deviceId">
        <span>名称：{{ item.name }}</span>
        <span>id：{{ item.deviceId }}</span>
        <div style="text-align: right;width: 100%;font-size: 20px;">
          <a @click="connectClick(item)" v-if="connectId !== item.deviceId">连接</a>
          <a @click="disConnect(item)" v-else style="color: red;">断开连接</a>
        </div>
      </div>
    </view>
  </view>
</template>

<script>
// 文档地址 https://uniapp.dcloud.net.cn/api/system/ble.html

export default {
  data() {
    return {
      // 蓝牙板子中的信息
      // connectId: "E4:65:B8:74:AB:3A",
      // SERVICE_UUID: "4fafc201-1fb5-459e-8fcc-c5c9c331914b",
      // CHARACTERISTIC_UUID: "beb5483e-36e1-4688-b7f5-ea07361b26a8",
      connectId: "",
      SERVICE_UUID: "",
      CHARACTERISTIC_UUID: "",

      // 是否已连接
      connected: false,
      inited: false,
      devices: [],
      // 当前连接的设备id
      // 接收到的数据
      received: ""
    }
  },
  onLoad() {
    this.main();
  },

  onUnload() {
    this.disConnect();
  },
  methods: {
    // 开启蓝牙
    initBlue() {
      console.log('初始化蓝牙开始...')
      const _this = this;
      return new Promise((resolve) => {
        uni.openBluetoothAdapter({
          success(res) {
            console.log('初始化蓝牙成功')
            _this.inited = true;
            // 扫描蓝牙设备
            _this.scan();
            resolve();
          },
          fail(err) {
            console.log('初始化蓝牙失败', err)
            uni.showModal({
              title: '失败提示',
              content: "初始化蓝牙失败",
              showCancel: false
            });
          }
        })
      })
    },
    async main() {
      // 初始化蓝牙
      await this.initBlue();
    },
    // 扫描设备
    async scan() {
      const _this = this;
      if (!_this.inited) {
        await _this.initBlue();
      }

      uni.startBluetoothDevicesDiscovery({
        success(res) {
          console.log('启动搜索')
        },
        fail(err) {
          console.log('启动搜索失败', err)
          uni.showModal({
            title: '失败提示',
            content: "启动搜索失败",
            showCancel: false
          });
        }
      })

      // 收到到蓝牙设备后的回调
      uni.onBluetoothDeviceFound(({devices}) => {
        _this.devices.push(...devices);
        // 去重
        _this.devices = [..._this.devices].reduce((pre, cur) => {
			console.log(cur.name)
          if ((pre.find(item => item.deviceId == cur.deviceId)) == null && (cur.name == "Arduino" || cur.name.startsWith("LMB-Robot"))) {
            pre.push(cur)
          }
          return pre;
        }, [])
      })
    },

    // 字符串转为ArrayBuffer对象，参数为字符串
    str2ab(str) {
      var buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节
      var bufView = new Uint16Array(buf);
      for (var i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
      }
      return buf;
    },

    // 点击某个设备的连接
    async connectClick(item) {
      console.log('即将连接蓝牙：', item);
      // 如果当前有连接的设备，需要先断开
      if (this.connected) {
        await this.disConnect();
      }
      if (!this.inited) {
        await this.initBlue();

        // 扫描蓝牙设备
        // this.scan();
      }
      this.connectId = item.deviceId;
      this.connect();
    },
    // 连接蓝牙
    connect() {
      const _this = this;
      console.log('设备id', _this.connectId);
      return new Promise((resolve) => {
        uni.showLoading({
          title: '连接中...'
        });
        uni.createBLEConnection({
          timeout: 5000,
          // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
          deviceId: _this.connectId,
          success(res) {
            console.log("连接成功:", res);

            // 必须延时 1000，不然拿不到服务信息
            setTimeout(() => {
              _this.connected = true;

              // 获取服务的 ID, 会有多个 ID, 使用在硬件中定义的那个
              // 这里有个问题问题，getBLEDeviceServices和getBLEDeviceCharacteristics如果拆出去后无法正常运行，所以只能写到 success 里面
              uni.getBLEDeviceServices({
                deviceId: _this.connectId,
                success(res) {
                  // console.log('所有服务:', res)
                  if (!res.services.length) {
                    uni.showModal({
                      title: '失败提示',
                      content: "获取的服务长度为 0 ",
                      showCancel: false
                    });
                    return;
                  }
                  // 这里一般是获取最后一个，板子里把自定义的放到了最后
                  // 但不是所有情况都这样，具体需要看板子代码里面的定义
                  _this.SERVICE_UUID = res.services[res.services.length - 1].uuid;

                  // 必须延时 100，不然拿不到特征信息
                  setTimeout(() => {
                    // 获取服务下的特征的 ID, 会有多个 ID, 使用在硬件中定义的那个
                    uni.getBLEDeviceCharacteristics({
                      deviceId: _this.connectId,
                      serviceId: _this.SERVICE_UUID,
                      success(res) {
                        // console.log('所有特征:', res);

                        // 停止扫描设备, 否则会浪费性能
                        uni.stopBluetoothDevicesDiscovery({})

                        // 一般直接获取第一个就行
                        _this.CHARACTERISTIC_UUID = res.characteristics[0].uuid;


                        // 读取设备的特征值
                        uni.readBLECharacteristicValue({
                          // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
                          deviceId: _this.connectId,
                          // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
                          serviceId: _this.SERVICE_UUID,
                          // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
                          characteristicId: _this.CHARACTERISTIC_UUID,
                          success(res) {
                            console.log('读取特征值成功:', res)
                          },
                          fail(err) {
                            console.log('读取特征值失败：', err)
                          }
                        })

                        // 监听特征值的变化
                        uni.notifyBLECharacteristicValueChange({
                          state: true, // 启用 notify 功能
                          // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
                          deviceId: _this.connectId,
                          // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
                          serviceId: _this.SERVICE_UUID,
                          // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
                          characteristicId: _this.CHARACTERISTIC_UUID,
                          success(res) {
                            console.log('监听特征值成功:', res)

                            // 持续监听特征值变化 必须写到这个回调中，不然一些便宜的板子在第二次更改特征后，uniapp 中接收不到。
                            uni.onBLECharacteristicValueChange(function (characteristic) {
                              const str_data = String.fromCharCode.apply(null, new Uint8Array(characteristic.value));
                              console.log("特征值：", str_data);
                              _this.received = str_data;
                            })
                          },
                          fail(err) {
                            console.log('监听特征值失败：', err)
                          }
                        })

                        // 监听蓝牙连接状态
                        uni.onBLEConnectionStateChange(function (res) {
                          // 该方法回调中可以用于处理连接意外断开等异常情况
                          console.log(`设备连接状态改变 ${res.deviceId}, connected: ${res.connected}`)
                        })
                      },
                      fail(err) {
                        console.log('获取特征值失败', err)
                        uni.showModal({
                          title: '失败提示',
                          content: "获取特征值失败 " + err.errMsg,
                          showCancel: false
                        });
                      }
                    })
                  }, 100)
                },
                fail(err) {
                  console.log('获取蓝牙服务失败', err)
                  uni.showModal({
                    title: '失败提示',
                    content: "获取蓝牙服务失败 " + err.errMsg,
                    showCancel: false
                  });
                }
              })

              resolve();
            }, 1000);
          },
          fail(err) {
            _this.connected = true;
            _this.connectId = null
            _this.SERVICE_UUID = null
            _this.CHARACTERISTIC_UUID = null
            console.log('连接服务失败：', err)
            uni.showModal({
              title: '失败提示',
              content: "连接服务失败 " + err.errMsg,
              showCancel: false
            });
          },
          complete() {
            uni.hideLoading();
          }
        })
      })
    },
    disConnect() {
      const _this = this;
      if (!_this.connectId) return;
      uni.showLoading({
        title: 'loading...'
      });
      return new Promise((resolve) => {
        uni.closeBLEConnection({
          deviceId: _this.connectId,
          success(res) {
            console.log("断开连接成功：", res)

            // 断开后必须释放蓝牙资源，不然再点击连接就连不上了
            // https://uniapp.dcloud.net.cn/api/system/bluetooth.html
            uni.closeBluetoothAdapter({
              success() {
                console.log('释放蓝牙资源成功')
                _this.connected = false;
                _this.connectId = null
                _this.SERVICE_UUID = null
                _this.CHARACTERISTIC_UUID = null;
                _this.inited = false;
                _this.received = false;


                // 延时下保险点
                setTimeout(() => {
                  uni.hideLoading();
                  resolve();
                }, 600)
              },
              fail() {
                uni.hideLoading();
              }
            })

          },
          fail(err) {
            uni.hideLoading();
            console.log('断开连接失败：', err)
            uni.showModal({
              title: '失败提示',
              content: "断开连接失败 " + err.errMsg,
              showCancel: false
            });
          }
        })
      })

    },
    send() {
      const _this = this;
      console.log("设备id:", this.connectId)
      console.log("服务id:", this.SERVICE_UUID)
      console.log("特征id:", this.CHARACTERISTIC_UUID)

      // 发送数据
      const reqData = _this.str2ab("USOFT-WIFI-5G:usoft@123%456");
      uni.writeBLECharacteristicValue({
        // 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
        deviceId: this.connectId,
        // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
        serviceId: this.SERVICE_UUID,
        // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
        characteristicId: this.CHARACTERISTIC_UUID,
        // 这里的value是ArrayBuffer类型
        value: reqData,
        success(res) {
          console.log('特征值写入成功：', res.errMsg)
        }
      })
    }
  }
}
</script>

<style>
.scan-btn {
  width: 200px;
  background-color: skyblue;
  margin-top: 12px;

}

.dev-list {
  border: 1px solid #ccc;
  margin: 6px;
  padding: 6px;
  box-sizing: border-box;
  min-height: 200px;
  background-color: #efefef;
}

.deviceItem {
  padding: 12px 0px;
  box-sizing: border-box;
  border-bottom: 1px solid #ccc;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.deviceItem span {
  padding: 3px;
  flex-basis: 50%;
  box-sizing: border-box;
  overflow: hidden;
  text-overflow: ellipsis;
}

.deviceItem a {
  color: blue;
  width: 100%;
  text-align: right;
  font-weight: 600;
}

.msg {
  padding: 6px;
  display: flex;
  align-items: center;
}

.msg input {
  border: 1px solid #ccc;
  height: 40px;
}

.msg button {
  flex: 1;
  margin-left: 12px;
}
</style>
